#include "csem_impl.h"
#include "cfg.h"
#include "cfg_csem.h"
#include "cfg_v4.h"
#include "common.h"
#include "csem_api.h"
#include "dist.h"
#include "sleep_api.h"
#include "sleep_share.h"
#include "vector.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CSEM_TYPE_SLEEP 0
#define CSEM_TYPE_WAKE 1

static dist_action_t *_dist = &ver_sel;
static uint32_t _csem_interval = 5;

static csem_sleep_t **_proc_csem_sleep(csem_sleep_t *slps, const size_t len,
                                       size_t *out_len) {
#define TP_PUSH(results, rlen, x)                                              \
  do {                                                                         \
    *(results + rlen++) = x;                                                   \
    if (rlen >= top)                                                           \
      abort();                                                                 \
  } while (0)

  size_t top = (size_t)(len * 1.5);
  csem_sleep_t **results = malloc(sizeof(csem_sleep_t *) * top);
  size_t rlen = 0;

  ssize_t last_wake_idx = -1;
  for (ssize_t i = len - 1; i >= 0; i--) {
    if (CSEM_TYPE_WAKE == (slps + i)->type) {
      last_wake_idx = i;
      break;
    }
  }

  uint8_t is_slp = 0;
  csem_sleep_t *wake_bean = NULL;

  for (size_t i = 0; i < len; i++) {
    csem_sleep_t *cur = slps + i;
    if (!is_slp && CSEM_TYPE_SLEEP == cur->type) {
      if (!rlen) {
        TP_PUSH(results, rlen, cur);
      } else {
        if (wake_bean &&
            cur->ts >=
                wake_bean->ts + DEF_EVAL(_dist, WAKE2SLEEP_INTERVAL_TIME)) {
          TP_PUSH(results, rlen, wake_bean);
          TP_PUSH(results, rlen, cur);
          wake_bean = NULL;
        }
      }
      is_slp = 1;
    } else if (is_slp) {
      if (CSEM_TYPE_WAKE == cur->type) {
        is_slp = 0;
        wake_bean = cur;
        if (-1 != last_wake_idx && i == (size_t)last_wake_idx) {
          TP_PUSH(results, rlen, wake_bean);
          continue;
        }
      } else if (CSEM_TYPE_SLEEP == cur->type) {
        if (wake_bean) {
          TP_PUSH(results, rlen, wake_bean);
          TP_PUSH(results, rlen, cur);
          wake_bean = NULL;
        } else {
          csem_sleep_t *last = *(results + rlen - 1);
          if (CSEM_TYPE_SLEEP == last->type) {
            memcpy(last, cur, sizeof(*last));
          }
        }
      }
    }
  }

  *out_len = rlen;
  if (0 == rlen) {
    free(results);
    return NULL;
  }
  return results;
}

static smp_hr_t *_hr_ts_inrange(const smp_hr_t *hr_list, size_t len,
                                uint64_t begin, uint64_t end, size_t *out_len,
                                double *oavg) {
  smp_hr_t *filter = malloc(sizeof(smp_hr_t) * len);
  size_t fl = 0;
  double avg = 0;

  for (size_t i = 0; i < len; i++) {
    const smp_hr_t *cur = hr_list + i;

    if (begin <= cur->ts && cur->ts < end) {
      memcpy(filter + fl++, cur, sizeof(*cur));
      avg += cur->rate;
    }
  }

  *oavg = avg / fl;
  *out_len = fl;
  return filter;
}

static alias_acts_ll_t *_sleep_stage(const dist_action_t *dist,
                                     const csem_sleep_t *sleep,
                                     const csem_sleep_t *wake,
                                     const smp_hr_t *hr_list, size_t hl) {
  uint64_t s_time = sleep->ts;
  uint64_t w_time = wake->ts;

  size_t fl;
  double avg_hr;
  smp_hr_t *data_list =
      _hr_ts_inrange(hr_list, hl, s_time, w_time, &fl, &avg_hr);
  if (!fl) {
    free(data_list);
    return NULL;
  }

  alias_acts_ll_t *as = malloc(sizeof(*as));
  as->start_time = s_time;
  as->end_time = w_time;
  as->avg_hr = avg_hr;

  as->slist = NULL;
  new_linklist(&as->slist);

  if (w_time <= s_time + DEF_EVAL(dist, STAGING_LIGHT_SLEEP_TIME)) {
    each_activity_t *t;

    t = malloc(sizeof(*t));
    t->begin = s_time;
    t->end = w_time;
    t->type = ENUM_SLEEP_STAGING_TYPE_NREM1;
    append_item(as->slist, t, NULL);
  } else {
    csem_seg(dist, s_time, w_time, data_list, fl, as->slist, &_csem_interval);
  }

  free(data_list);
  return as;
}

static uint64_t _merge(const dist_action_t *dist, const alias_acts_ll_t *it,
                       uint64_t last_end_time, linked_list_t *staging_ll) {
#define APPEND_SUB(src, begin, end)                                            \
  for (uint32_t i = (begin); i < (end); i++) {                                 \
    each_activity_t *t = malloc(sizeof(*t));                                   \
    memcpy(t, (src) + i, sizeof(*t));                                          \
    append_item(temp_eas, t, NULL);                                            \
  }

  uint32_t cnt;
  each_activity_t *detail_list =
      iter_copy(it->slist, sizeof(each_activity_t), &cnt, NULL);

  if (!(0 != last_end_time &&
        it->start_time <=
            last_end_time + DEF_EVAL(dist, SLEEP_RANGE_MERGE_TIME))) {
    activity_summary_t *cur = malloc(sizeof(*cur));
    cur->begin = it->start_time;
    cur->end = it->end_time;
    cur->avg_hr = it->avg_hr;
    cur->act_list = detail_list;
    cur->cnt_acts = cnt;

    append_item(staging_ll, cur, NULL);
    return cur->end;
  }

  activity_summary_t *tail = index_item(staging_ll, -1, NULL);
  uint64_t wake_start = tail->end;

  linked_list_t *temp_eas = NULL;
  new_linklist(&temp_eas);

  if (ENUM_SLEEP_STAGING_TYPE_WAKE ==
      (tail->act_list + tail->cnt_acts - 1)->type) {
    wake_start = (tail->act_list + tail->cnt_acts - 1)->begin;

    APPEND_SUB(tail->act_list, 0, tail->cnt_acts - 1)
  } else {
    APPEND_SUB(tail->act_list, 0, tail->cnt_acts)
  }

  each_activity_t *t = NULL;
  if (ENUM_SLEEP_STAGING_TYPE_WAKE == detail_list->type) {
    t = malloc(sizeof(*t));

    t->begin = wake_start;
    t->end = detail_list->end;
    t->type = ENUM_SLEEP_STAGING_TYPE_WAKE;

    append_item(temp_eas, t, NULL);
    APPEND_SUB(detail_list, 1, cnt)
  } else {
    t = malloc(sizeof(*t));
    t->begin = wake_start;
    t->end = it->start_time;
    t->type = ENUM_SLEEP_STAGING_TYPE_WAKE;

    append_item(temp_eas, t, NULL);
    APPEND_SUB(detail_list, 0, cnt)
  }

  free(detail_list);

  tail->end = it->end_time;
  tail->avg_hr = (tail->avg_hr + it->avg_hr) / 2;

  if (tail->cnt_acts) {
    free(tail->act_list);
  }

  tail->act_list =
      iter_copy(temp_eas, sizeof(each_activity_t), &tail->cnt_acts, NULL);
  free_items(temp_eas, NULL, 0, NULL);
  return it->end_time;
}

void csem_impl(csem_sleep_t *csem_slps, size_t slp_len,
               const smp_hr_t *orig_hr_list, size_t hr_len, sleep_root **root) {
  _dist->csem_ext = &csem_ei;
  if (!root)
    return;

  smp_hr_t *hr_list = _dist->pre_proc(orig_hr_list, hr_len, &hr_len);

  double low_data_avg;
  double all_data_avg = all_avg(hr_list, hr_len, &low_data_avg);

  sleep_root *target = (sleep_root *)malloc(sizeof(sleep_root));
  *root = target;

  target->avg_hr = all_data_avg;
  target->resting_hr = low_data_avg;

  csem_sleep_t **slp_wake_list =
      _proc_csem_sleep(csem_slps, slp_len, &slp_len /*不再依赖slps*/);

  if (!slp_len) {
    target->summaries = NULL;
    target->count = 0;

    _dist->pre_release(hr_list);
    return;
  }

  linked_list_t *ll_as = NULL;
  new_linklist(&ll_as);

  const csem_sleep_t *sleep_data = NULL;
  for (size_t i = 0; i < slp_len; i++) {
    const csem_sleep_t *it = *(slp_wake_list + i);
    if (!sleep_data) {
      if (CSEM_TYPE_SLEEP == it->type) {
        sleep_data = it;
      }
    } else {
      if (CSEM_TYPE_WAKE == it->type) {
        alias_acts_ll_t *as =
            _sleep_stage(_dist, sleep_data, it, hr_list, hr_len);
        if (as) {
          append_item(ll_as, as, NULL);
        }
        sleep_data = NULL;
      } else if (CSEM_TYPE_SLEEP == it->type) {
        sleep_data = it;
      } else {
        sleep_data = NULL;
      }
    }
  }

  free(slp_wake_list);

  uint32_t cnt;
  alias_acts_ll_t *alias_list =
      iter_copy(ll_as, sizeof(alias_acts_ll_t), &cnt, NULL);

  free_items(ll_as, NULL, 0, NULL);

  uint64_t last_end_time = 0;
  linked_list_t *ll_summary = NULL;
  new_linklist(&ll_summary);

  for (uint32_t i = 0; i < cnt; i++) {
    alias_acts_ll_t *cur = alias_list + i;
    last_end_time = _merge(_dist, cur, last_end_time, ll_summary);
    free_items(cur->slist, NULL, 0, NULL);
  }

  free(alias_list);
  target->summaries =
      iter_copy(ll_summary, sizeof(activity_summary_t), &target->count, NULL);

  free_items(ll_summary, NULL, 0, NULL);

  for (uint32_t i = 0; i < target->count; i++) {
    activity_summary_t *cur = target->summaries + i;
    if (cur->end <= cur->begin + DEF_EVAL(_dist, SLEEP_FRAGMENT_MIN_NAP_TIME)) {
      if (cur->cnt_acts)
        free(cur->act_list);

      cur->cnt_acts = 1;
      cur->act_list = malloc(sizeof(each_activity_t));

      cur->act_list->begin = cur->begin;
      cur->act_list->end = cur->end;
      cur->act_list->type = ENUM_SLEEP_STAGING_TYPE_NAP;
    }
  }

  _dist->pre_release(hr_list);
}

uint32_t csem_interval() { return _csem_interval; }

static double _F_N1_MOTION_THRESHOLD() { return 10 * csem_interval(); }

csem_ext_t csem_ei = {
    ._CSEM_M(STAGING_LIGHT_SLEEP_TIME) = 30 * 60 * 1000,
    // 间隔睡眠周期时间在合并时间内,合并睡眠时间
    ._CSEM_M(SLEEP_RANGE_MERGE_TIME) = 1 * 60 * 60 * 1000,
    // 多少分钟内的睡眠段数据算零星小睡 NAP
    ._CSEM_M(SLEEP_FRAGMENT_MIN_NAP_TIME) = 1 * 60 * 60 * 1000,
    // CSEM 算法，苏醒后又入睡，在这个时间内的话算是异常
    ._CSEM_M(WAKE2SLEEP_INTERVAL_TIME) = 3 * 60 * 1000,
    // CSEM算法计算的，入睡苏醒时间，判断如果小于这个时间的，不算做睡眠
    ._CSEM_M(MINIMUM_SLEEP_TIME) = 20 * 60 * 1000,
    /**
     * N3期运动数据阈值
     * 判断的所有数据motion如果有超过这个值的，非N3
     */
    ._CSEM_M(N3_MOTION_THRESHOLD) = (0 + 2),
    /**
     * N1,N2LightSleep期运动数据总和阈值
     * 超过这个阈值是REM
     */
    ._CSEM_M(N1_MOTION_THRESHOLD) = _F_N1_MOTION_THRESHOLD,
    // 最小分期时间，小于该时间的数据标为LightSleep
    ._CSEM_M(MIN_STAGING_TIME) = 30 * 60 * 1000,
};